package com.bangcommunity.bbframe.common.utils.crypt;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CryptUtils {
    public static final String AES_CBC_NO_PADDING = "AES/CBC/NoPadding";
    public static final String AES = "AES";
    public static final Charset CHAR_SET_UTF_8 = Charset.forName("UTF-8");
    private static Logger logger = LoggerFactory.getLogger(CryptUtils.class);
    /*
     * 16进制数字字符集
     */
    private static String hexString = "0123456789ABCDEF";

    /**
     * 取字节数组的md5值的十六进制串表示
     *
     * @param b
     * @return
     */
    public static String getMd5str(byte[] b) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            return bytetoHexStringLowwer(md.digest(b));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 取字节数组的md5值的十六进制串表示
     *
     * @return
     * @throws UnsupportedEncodingException
     */
    public static String getMd5str(String str) {
        try {
            return getMd5str(str.getBytes("UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 取字节数组的md5值的十六进制串表示
     *
     * @throws UnsupportedEncodingException
     */
    public static String getMd5str(Object... strs) {
        StringBuffer sbuf = new StringBuffer();
        if (null != strs) {
            for (Object s : strs) {
                sbuf.append(s != null ? s.toString() : "");
            }
        }
        return getMd5str(sbuf.toString());
    }

    /**
     * 将字节数组，转换为字符串
     *
     * @param data
     * @return
     */
    public static String bytetoHexStringUpper(byte[] data) {
        char hexdigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        return bytetoHexString(data, hexdigits);
    }

    /**
     * 将字节数组，转换为字符串
     *
     * @param data
     * @return
     */
    public static String bytetoHexStringLowwer(byte[] data) {
        char hexdigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
        return bytetoHexString(data, hexdigits);
    }

    private static String bytetoHexString(byte[] data, char[] hexdigits) {
        char[] tem = new char[data.length * 2];
        for (int i = 0; i < data.length; i++) {
            byte b = data[i];
            tem[i * 2] = hexdigits[b >>> 4 & 0x0f];
            tem[i * 2 + 1] = hexdigits[b & 0x0f];
        }
        return new String(tem);
    }

    /**
     * 通过aes加密
     *
     * @param data
     * @throws UnsupportedEncodingException
     */
    public static byte[] aesEncrypt(byte[] data, String password) {
        try {
            KeyGenerator keyG = KeyGenerator.getInstance("AES");
            SecureRandom instance = SecureRandom.getInstance("SHA1PRNG");
            instance.setSeed(password.getBytes("UTF-8"));
            keyG.init(128, instance);
            SecretKey key = keyG.generateKey();
            byte[] encodeFormat = key.getEncoded();
            SecretKeySpec sks = new SecretKeySpec(encodeFormat, "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, sks);
            return cipher.doFinal(data);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (NoSuchPaddingException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
        } catch (IllegalBlockSizeException e) {
            throw new RuntimeException(e);
        } catch (BadPaddingException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 通过aes解密
     *
     * @param data
     * @return
     */
    public static byte[] aesDecrypt(byte[] data, String password) {
        try {
            KeyGenerator keyG = KeyGenerator.getInstance("AES");
            SecureRandom instance = SecureRandom.getInstance("SHA1PRNG");
            instance.setSeed(password.getBytes("UTF-8"));
            keyG.init(128, instance);
            SecretKey key = keyG.generateKey();
            byte[] encodeFormat = key.getEncoded();
            SecretKeySpec sks = new SecretKeySpec(encodeFormat, "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, sks);
            return cipher.doFinal(data);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (NoSuchPaddingException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
        } catch (IllegalBlockSizeException e) {
            throw new RuntimeException(e);
        } catch (BadPaddingException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String sha1Encoder(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        return bytetoHexStringLowwer(messageDigestEncrypt(str.getBytes("UTF-8"), "SHA1"));
    }

    public static String sha1Digest(String str) {
        try {
            return bytetoHexStringLowwer(messageDigestEncrypt(str.getBytes("UTF-8"), "SHA1"));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static String sha1Digest(String str, String encode) {
        try {
            return bytetoHexStringLowwer(messageDigestEncrypt(str.getBytes(encode), "SHA1"));
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static String sha1Strings(Object... strs) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        StringBuffer sbuf = new StringBuffer();
        if (null != strs) {
            for (Object s : strs) {
                sbuf.append(s != null ? s.toString() : "");
            }
        }
        return sha1Encoder(sbuf.toString());
    }

    public static byte[] messageDigestEncrypt(byte[] data, String key) throws NoSuchAlgorithmException {
        MessageDigest sha = MessageDigest.getInstance(key);
        sha.update(data);
        return sha.digest();
    }

    public static byte[] hmacSha(String key, byte[] datas, String secret) {
        try {
            if (null == key) {
                key = "HmacSHA1";
            }
            // Get an hmac_sha1 key from the raw key bytes
            byte[] keyBytes = key.getBytes();
            SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA1");

            // Get an hmac_sha1 Mac instance and initialize with the signing key
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(signingKey);
            // Compute the hmac on input data bytes
            return mac.doFinal(datas);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] hmacSha256(byte[] datas, String secret) {
        try {
            // Get an hmac_sha1 key from the raw key bytes
            byte[] keyBytes = secret.getBytes();
            SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA256");

            // Get an hmac_sha1 Mac instance and initialize with the signing key
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(signingKey);
            // Compute the hmac on input data bytes
            return mac.doFinal(datas);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static int parse(char c) {
        if (c >= 'a')
            return (c - 'a' + 10) & 0x0f;
        if (c >= 'A')
            return (c - 'A' + 10) & 0x0f;
        return (c - '0') & 0x0f;
    }

    // 从十六进制字符串到字节数组转换
    public static byte[] hexString2Bytes(String hexstr) {
        byte[] b = new byte[hexstr.length() / 2];
        int j = 0;
        for (int i = 0; i < b.length; i++) {
            char c0 = hexstr.charAt(j++);
            char c1 = hexstr.charAt(j++);
            b[i] = (byte) ((parse(c0) << 4) | parse(c1));
        }
        return b;
    }

    public static String aesIvEncode(String dataEncrypt, String key, String iv) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance(AES_CBC_NO_PADDING);

            byte[] dataBytes = dataEncrypt.getBytes(CHAR_SET_UTF_8);

            int plaintextLength = dataBytes.length;
            int blockSize = cipher.getBlockSize();
            if (plaintextLength % blockSize != 0) {
                plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
            }
            byte[] plaintext = new byte[plaintextLength];

            System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);

            SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(CHAR_SET_UTF_8), AES);
            IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes(CHAR_SET_UTF_8));
            cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
            byte[] encrypted = cipher.doFinal(plaintext);
            return bytetoHexStringLowwer(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String aesIvDecode(String dataDesEncrypt, String key, String iv) throws Exception {
        try {
            byte[] encrypted1 = hexString2Bytes(dataDesEncrypt);
            Cipher cipher = Cipher.getInstance(AES_CBC_NO_PADDING);
            SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(CHAR_SET_UTF_8), "AES");
            IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes(CHAR_SET_UTF_8));

            cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);

            byte[] original = cipher.doFinal(encrypted1);
            int i = 0;
            int c = original.length;
            while (original[--c] == 0 && c >= 0) {
                i++;
            }
            int ocLength = original.length - i;
            byte[] origCopy = new byte[ocLength];
            System.arraycopy(original, 0, origCopy, 0, ocLength);

            String originalString = new String(origCopy, CHAR_SET_UTF_8);

            return originalString;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /*
     * 将字符串编码成16进制数字,适用于所有字符（包括中文）
     */
    public static String encodeHexString(String str) {
        // 根据默认编码获取字节数组
        byte[] bytes = str.getBytes();
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        // 将字节数组中每个字节拆解成2位16进制整数
        for (int i = 0; i < bytes.length; i++) {
            sb.append(hexString.charAt((bytes[i] & 0xf0) >> 4));
            sb.append(hexString.charAt((bytes[i] & 0x0f) >> 0));
        }
        return sb.toString();
    }

    /*
     * 将16进制数字解码成字符串,适用于所有字符（包括中文）
     */
    public static String decodeHexString(String bytes) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length() / 2);
        // 将每2位16进制整数组装成一个字节
        for (int i = 0; i < bytes.length(); i += 2)
            baos.write((hexString.indexOf(bytes.charAt(i)) << 4 | hexString.indexOf(bytes.charAt(i + 1))));
        return new String(baos.toByteArray());
    }

    public static String unescapeUnicode(String str) {
        StringBuffer b = new StringBuffer();
        Matcher m = Pattern.compile("\\u([0-9a-fA-F]{4})").matcher(str);
        while (m.find())
            b.append((char) Integer.parseInt(m.group(1), 16));
        return b.toString();
    }

    public static String encodeUnicode(final String gbString) {
        char[] utfBytes = gbString.toCharArray();
        String unicodeBytes = "";
        for (int byteIndex = 0; byteIndex < utfBytes.length; byteIndex++) {
            String hexB = Integer.toHexString(utfBytes[byteIndex]);
            if (hexB.length() <= 2) {
                hexB = "00" + hexB;
            }
            unicodeBytes = unicodeBytes + "\\u" + hexB;
        }
        return unicodeBytes;
    }

    public static String decodeUnicode(final String dataStr) {
        int end = 0;
        final StringBuffer buffer = new StringBuffer();
        int start = dataStr.indexOf("\\u");
        if (start > 0) {
            buffer.append(dataStr.substring(0, start));
        }
        int tail = start + 6;
        while (start > -1) {
            end = dataStr.indexOf("\\u", start + 2);
            String charStr = "";
            if (end == -1) {
                charStr = dataStr.substring(start + 2, dataStr.length());
            } else {
                charStr = dataStr.substring(start + 2, end);
            }
            char letter = (char) Integer.parseInt(charStr, 16); // 16进制parse整形字符串。
            buffer.append(new Character(letter).toString());
            tail = start + 6;
            start = end;
        }
        return buffer.toString();
    }

    public static void main(String[] args)
            throws NoSuchAlgorithmException, UnsupportedEncodingException, IOException, NoSuchProviderException {
        System.out.println(CryptUtils.getMd5str("player" + "login"));
        System.out.println(CryptUtils.getMd5str("player" + "pay"));
        System.out.println(unescapeUnicode("geotable_id:\u53c2\u6570\u5fc5\u9700"));
        System.out.println(decodeUnicode("geotable_id:\u53c2\u6570\u5fc5\u9700"));
    }
}
